// 04-myPromise.js: 手写实现MyPromise源码
/**
 * 1.Promise本身就是一个类，在执行这个类的时候，需要传递一个执行器进去，执行器立即执行
 * 2.then方法内部做的事情就判断状态，如果状态是成功 就调用成功的回调函数；如果状态是失败就调用失败的回调函数
 * 3.then方法是被定义在原型对象中的
 * 4.then方法成功回调有一个参数，表示成功之后的值；then方法失败回调有一个参数，表示失败之后的原因
 */

let promise = new Promise((resolve, reject) => {
    resolve('成功');//成功
    // reject('失败');//失败
});
promise.then(() => { }, () => { });


const PENDING = 'pending';//等待
const FULFILLED = 'fulfilled';//成功
const REJECTED = 'rejected';//失败

class MyPromise {

    //用constructor去接收这个执行器(执行器：指的就是回调函数)
    constructor(executor) {
        /**
         * 1.该执行器会立即执行，所以在这里立即调用它
         * 2.在调用这个执行器的时候，传递进去两个方法：resolve、reject
         */
        //捕获执行器中的错误try...catch
        try {
            executor(this.resolve, this.reject);
        } catch (e) {
            this.reject(e);
        }
    }

    //添加status属性，代表promise状态，初始值是等待:pending
    status = PENDING;

    //成功之后的值
    value = undefined;

    //失败之后的原因
    reason = undefined;

    //成功回调
    successCallback = [];

    //失败回调
    failCallback = [];

    //添加resolve属性(定义为箭头函数的目的：使resolve的this属性指向这个promise对象)
    resolve = (value) => {

        //状态一旦确定，则不可更改。即：如果状态不是PENDING，则阻止向下执行
        if (this.status !== PENDING) return;

        //将状态更改为成功
        this.stauts = FULFILLED;

        //保存成功之后的值(原因：要在then方法中使用)
        this.value = value;

        //判断成功回调是否存在，如果存在则调用
        while (this.successCallback.length) this.successCallback.shift()();
    }

    //添加reject属性(定义为箭头函数的目的：使reject的this属性指向这个promise对象)
    reject = (value) => {

        //状态一旦确定，则不可更改。即：如果状态不是PENDING，则阻止向下执行
        if (this.status !== PENDING) return;

        //将状态更改为失败
        this.stauts = REJECTED;

        //保存失败之后的原因(原因：要在then方法中使用)
        this.reason = value;

        //判断失败回调是否存在，如果存在则调用
        while (this.failCallback.length) this.failCallback.shift()();
    }

    then(successCallback, failCallback) {

        //调用then方法时，then方法可以不传递参数，这个promise状态会一直传递，直到传递给有回调函数then方法。
        successCallback = successCallback ? successCallback : value => value;
        failCallback = failCallback ? failCallback : reason => { throw reason };

        //then方法会返回一个promise对象
        let promise2 = new MyPromise((resolve, reject) => {

            if (this.stauts === FULFILLED) {

                setTimeout(() => {

                    try {

                        //如果状态返回成功，则调用成功回调函数
                        let x = successCallback(this.value);
                        /**
                         * 1.判断 x 的值是普通值还是promise对象
                         * 2.如果是普通纸，则直接调用resolve
                         * 3.如果是promise对象，则查看promise对象的返回结果。再根据promise对象的返回结果决定调用resolve，还是调用reject
                         */

                        /**
                         * 注意：这里是在实例 promise2 的过程当中获取的promise2，所以这时是获取不到promise2的。
                         * 所以需要把获取promise2的方法改为异步(当同步代码都执行完时，再执行异步代码)，所以使用setTimeout
                         */

                        resolvePromise(promise2, x, resolve, reject);

                    } catch (e) {
                        reject(e);
                    }

                }, 0);

            } else if (this.stauts === REJECTED) {

                //如果状态返回失败，则调用失败回调函数

                setTimeout(() => {

                    try {

                        //如果状态返回成功，则调用成功回调函数
                        let x = failCallback(this.reason);
                        /**
                         * 1.判断 x 的值是普通值还是promise对象
                         * 2.如果是普通纸，则直接调用resolve
                         * 3.如果是promise对象，则查看promise对象的返回结果。再根据promise对象的返回结果决定调用resolve，还是调用reject
                         */

                        /**
                         * 注意：这里是在实例 promise2 的过程当中获取的promise2，所以这时是获取不到promise2的。
                         * 所以需要把获取promise2的方法改为异步(当同步代码都执行完时，再执行异步代码)，所以使用setTimeout
                         */

                        resolvePromise(promise2, x, resolve, reject);

                    } catch (e) {
                        reject(e);
                    }

                }, 0);

            } else {

                //返回状态为PENDING(异步情况下)，此时不确定要执行成功回调方法还是执行失败回调方法
                this.successCallback.push(() => {
                    setTimeout(() => {

                        try {

                            //如果状态返回成功，则调用成功回调函数
                            let x = successCallback(this.value);
                            /**
                             * 1.判断 x 的值是普通值还是promise对象
                             * 2.如果是普通纸，则直接调用resolve
                             * 3.如果是promise对象，则查看promise对象的返回结果。再根据promise对象的返回结果决定调用resolve，还是调用reject
                             */

                            /**
                             * 注意：这里是在实例 promise2 的过程当中获取的promise2，所以这时是获取不到promise2的。
                             * 所以需要把获取promise2的方法改为异步(当同步代码都执行完时，再执行异步代码)，所以使用setTimeout
                             */

                            resolvePromise(promise2, x, resolve, reject);

                        } catch (e) {
                            reject(e);
                        }

                    }, 0);
                });
                this.failCallback.push(() => {

                    setTimeout(() => {

                        try {

                            //如果状态返回成功，则调用成功回调函数
                            let x = failCallback(this.reason);
                            /**
                             * 1.判断 x 的值是普通值还是promise对象
                             * 2.如果是普通纸，则直接调用resolve
                             * 3.如果是promise对象，则查看promise对象的返回结果。再根据promise对象的返回结果决定调用resolve，还是调用reject
                             */

                            /**
                             * 注意：这里是在实例 promise2 的过程当中获取的promise2，所以这时是获取不到promise2的。
                             * 所以需要把获取promise2的方法改为异步(当同步代码都执行完时，再执行异步代码)，所以使用setTimeout
                             */

                            resolvePromise(promise2, x, resolve, reject);

                        } catch (e) {
                            reject(e);
                        }

                    }, 0);

                });

            }

        });

        return promise2;
    }

    //finally(不是静态方法)
    /**
     * 1.无论这个promise对象最终返回结果是成功还是失败，finally函数始终都会被执行一次
     * 2.可以连续链式调用then方法(说明，finally返回的是一个promise对象)获取到当前promise的最终返回结果
     */
    finally(callback) {
        return this.then(value => {
            return MyPromise.resolve(callback()).then(() => value);
        }, reason => {
            return MyPromise.resolve(callback()).then(() => { throw reason });
        });
    }

    //catch用来处理当前的promise方法为错误的情况的
    catch(failCallback) {
        return this.then(undefined, failCallback);
    }

    //静态方法Promise.all
    /**
     * 1.all方法是用来解决异步并发问题的，通过异步代码调用的顺序得到异步代码执行的结果。
     * 2.all方法的返回值是一个promise对象
     * 3.all方法接收一个数组作为参数，所以我们需要定义一个形参array
     */
    static all(array) {

        /**
         * 循环array数组，在循环的过程中，我们需要检查这个元素是一个普通值还是一个promise对象
         * 如果是一个普通值，则把这个值直接放入到结果数组当中
         * 如果是一个promise对象，则先去执行这个promise对象，然后再把promise对象的结果，放入结果数组当中
         */

        let result = [];
        let index = 0;

        return new MyPromise((resolve, reject) => {

            //addData方法的作用就是将数值放入结果数组当中
            function addData(key, value) {
                result[key] = value;
                index++;
                if (index == array.length) {
                    resolve(result);
                }
            }
            for (let i = 0; i < array.length; i++) {
                let current = array[i];

                //判断数组元素是否是MyPromise下面的一个实例
                if (current instanceof MyPromise) {
                    //是promise对象
                    current.then(value => addData(i, value), reason => reject(reason));
                } else {
                    //是普通值
                    addData(i, array[i]);
                }
            }
        });

    }

    //静态方法Promise.resolve方法
    /**
     * 1.Promise.resolve方法是将给定的值转换为promise对象，也就是说Promise.resolve的返回值就是一个promise对象，在返回的对象中就包裹给定的这个值
     * 2.如果传入的值是一个promise对象，则原封不动的返回该对象
     */
    static resolve(value) {
        if (value instanceof MyPromise) {
            //  是promise对象
            return value;
        } else {
            //是普通值
            return new MyPromise(resolve => resolve(value));
        }
    }

}

function resolvePromise(promise2, x, resolve, reject) {

    //判断promise对象是否是自返回
    if (promise2 === x) {
        return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
    }

    //判断 x 是否是 MyPromise 对象的类
    if (x instanceof MyPromise) {
        //是promise对象
        // x.then(value => resolve(value), reason => reject(reason));
        x.then(resolve, reject);
    } else {
        //是普通值
        resolve(x);
    }

}


module.exports = MyPromise;